<?php

namespace App\Services\Repositories\SkinRecord;

use App\Models\Admin\StoreAdmin;
use App\Models\OpenWechat\Authorizer;
use App\Models\OpenWechat\WechatTemplateNotice;
use App\Models\OpenWechat\WechatUser;
use App\Models\Panx\SkinRecordBonus;
use App\Models\Store\SkinRecord;
use App\Services\AliPay\AopFactory;
use App\Services\OpenWechat\MerchantPay;
use App\Services\OpenWechat\TemplateNotice;
use App\Services\Repositories\WechatTemplateNoticeRepository;
use App\Services\Utils\Random;
use App\Services\Utils\TimeHelper;
use App\Services\Utils\Transaction;


/**
 * 肤测记录
 * Class SkinRecordRepository
 * @package App\Services\SkinRecord
 */
class SkinRecordBonusRepository
{
    /**
     * @var SkinRecord
     */
    protected $skin_record_bonus;

    const URL = '%s/wx-system?%s&appid=%s#/lucky-money-user-records';


    public function __construct()
    {
        $this->skin_record_bonus = new SkinRecordBonus();
    }

    /**
     * @param SkinRecord $skinRecord
     * @return bool
     * @throws \Exception
     */
    public function recordSkinRecordBonus(SkinRecord $skinRecord)
    {
        $can_grant = (new SkinRecordBonusGrantCheck)->canGrantBonus($skinRecord);
        if (! $can_grant) {
            return false;
        }

        $skinRecordBonuses = $this->createBonuses($skinRecord);
        if (! $skinRecordBonuses) {
            return false;
        }

        try { // 可能失败， 不应该影响测肤
            $this->sendBonusAndTplNotice($skinRecordBonuses);
        } catch (\Exception $e) {
            logger($e);
        }

        return true;
    }

    /**
     * @param SkinRecord $skinRecord
     * @param $pay_type
     * @return mixed|SkinRecordBonus
     */
    private function createStoreAdmin(SkinRecord $skinRecord, int $pay_type)
    {
        $amount = Random::lottery([
            '0.75' => [1, 2],  // 75%
            '0.95' => [2, 5],  // 20%
            '1'    => [5, 10]  // 5%
        ]);

        $created = $this->skin_record_bonus->create([
            'bonusable_id'   => $skinRecord->store_admin_id,
            'bonusable_type' => class_basename(StoreAdmin::class),
            'amount'         => $amount,
            'skin_record_id' => $skinRecord->id,
            'pay_type'       => $pay_type,
            'biz_no'         => TimeHelper::microTimeSerial(),
        ]);

        return (new SkinRecordBonus)->find($created->id);
    }

    /**
     * @param SkinRecord $skinRecord
     * @param $pay_type
     * @return mixed|SkinRecordBonus
     */
    private function createWechatUser(SkinRecord $skinRecord, int $pay_type)
    {
        $amount = $this->getWechatUserBonusAmount($skinRecord, $pay_type);

        $created = $this->skin_record_bonus->create([
            'bonusable_id'   => $skinRecord->wechat_user_id,
            'bonusable_type' => class_basename(WechatUser::class),
            'amount'         => $amount,
            'skin_record_id' => $skinRecord->id,
            'pay_type'       => $pay_type,
            'biz_no'         => TimeHelper::microTimeSerial(),
        ]);

        return (new SkinRecordBonus)->find($created->id);
    }

    /**
     * @param SkinRecord $skinRecord
     * @return int
     */
    private function getPayType(SkinRecord $skinRecord): int
    {
        $author_id = $skinRecord->getAuthor()->id ?? null;
        if ($author_id) {
            $pay_type = $author_id === Authorizer::ID_MAGIC_MIRROR
                ? SkinRecordBonus::BONUS_WX_PAY_TYPE
                : SkinRecordBonus::BONUS_ALI_PAY_TYPE;
        } else {
            $pay_type = SkinRecordBonus::BONUS_ALI_PAY_TYPE;
        }

        return $pay_type;
    }


    /**
     * 发红包
     *
     * @test
     *
     * @param SkinRecordBonus $skinRecordBonus
     * @return bool
     * @throws \Exception
     */
    public function grantBonusByPayType(SkinRecordBonus $skinRecordBonus): bool
    {
        if ($skinRecordBonus->isZeroAmount()) {
            return false;
        }

        if ($skinRecordBonus->isUseWxPay()) {
            $update = $this->grantWxPayBonus($skinRecordBonus);
        } elseif ($skinRecordBonus->isUseAliPay()) {
            $update = $this->grantAlipayBonus($skinRecordBonus);
        } else {
            $update = false;
        }

        return $update;
    }


    /**
     * 发模板消息
     * @test
     *
     * @param SkinRecordBonus $skinRecordBonus
     * @return array|bool
     * @throws \Exception
     */
    public function sendTemplateNotice(SkinRecordBonus $skinRecordBonus): ?array
    {
        $author = $skinRecordBonus->getTemplateNoticeAuthor();
        if (! $author) {
            return null;
        }

        $template_id = (new WechatTemplateNoticeRepository())
            ->getAuthorizerTemplateId($author, WechatTemplateNotice::ID_SHORT_LOTTERY);
        if (! $template_id) {
            return null;
        }

        $notice = new TemplateNotice($author->authorizer_appid);

        $wechatUser = $this->getBonusWechatUser($skinRecordBonus);
        $openid = $wechatUser->openid;
        $url_param = $this->getBonusNoticeUrl($skinRecordBonus, $wechatUser);
        $msg = $this->getBonusNoticeMessage($skinRecordBonus);

        $res = $notice->lotteryNotice($openid, $url_param, $msg, $template_id);

        return $res;
    }

    /**
     * @param SkinRecord $skinRecord
     * @return array
     * @throws \Exception
     */
    private function createBonuses(SkinRecord $skinRecord): ?array
    {
        $pay_type = $this->getPayType($skinRecord);

        $skinRecordBonuses = Transaction::handle(function () use ($skinRecord, $pay_type) {
            $storeAdmin = $this->createStoreAdmin($skinRecord, $pay_type);

            $wechatUser = $this->createWechatUser($skinRecord, $pay_type);

            return compact('storeAdmin', 'wechatUser');
        }, false, null);

        return $skinRecordBonuses;
    }

    /**
     * 发送红包&模板消息
     * @param $skinRecordBonuses
     * @return bool
     * @throws \Exception
     */
    private function sendBonusAndTplNotice(array $skinRecordBonuses): bool
    {
        // 店员 发送红包 和 模板消息
        $this->grantBonusByPayType($skinRecordBonuses['storeAdmin']);
        $this->sendTemplateNotice($skinRecordBonuses['storeAdmin']);

        // 测肤用户 发红包 和 模板消息
        $this->grantBonusByPayType($skinRecordBonuses['wechatUser']);
        $this->sendTemplateNotice($skinRecordBonuses['wechatUser']);

        return true;
    }

    /**
     * @test
     *
     * @param SkinRecordBonus $skinRecordBonus
     *
     * @return bool
     * @throws \Exception
     */
    protected function grantWxPayBonus(SkinRecordBonus $skinRecordBonus): bool
    {
        $pay = new MerchantPay(Authorizer::defaultMerchant());

        $result = $pay->transferToBalanceForSkinRecordBonus($skinRecordBonus);

        if ($result['return_code'] === MerchantPay::RET_SUCCESS &&
            $result['result_code'] === MerchantPay::RET_SUCCESS)
        {
            $bonus_status = SkinRecordBonus::BONUS_STATUS_SUCCESS;
        } else {
            $bonus_status = SkinRecordBonus::BONUS_STATUS_FAILED;
        }

        $update = $skinRecordBonus->update([
            'data'         => $result,
            'bonus_status' => $bonus_status
        ]);

        return $update;
    }

    /**
     * @test
     *
     * @param SkinRecordBonus $skinRecordBonus
     *
     * @return bool
     * @throws \Exception
     */
    protected function grantAlipayBonus(SkinRecordBonus $skinRecordBonus): bool
    {
        $pay = new AopFactory();
        // 没有 APPid 会直接抛错 中断程序
        $response = $pay->transferRequestForSkinRecordBonus($skinRecordBonus);

        $res_code = (string) $response->alipay_fund_trans_toaccount_transfer_response->code;
        if ($res_code === AopFactory::TRANSFER_SUCCESS) {
            $bonus_status = SkinRecordBonus::BONUS_STATUS_SUCCESS;
        } else {
            $bonus_status = SkinRecordBonus::BONUS_STATUS_FAILED;
        }

        $update = $skinRecordBonus->update([
            'data'         => $response,
            'bonus_status' => $bonus_status
        ]);

        return $update;
    }

    /**
     * @param SkinRecordBonus $skinRecordBonus
     * @param $wechatUser
     *
     * @return string
     */
    protected function getBonusNoticeUrl(SkinRecordBonus $skinRecordBonus,
                                         WechatUser $wechatUser): string
    {
        $appid = optional($wechatUser->authorizer)->authorizer_appid;
        $openid = $wechatUser->openid;
        $store_admin_id = $skinRecordBonus->bonusable->id;

        return $skinRecordBonus->isToWechatUser()
            ? sprintf(self::URL, config('app.url'), 'openid=' . $openid, $appid)
            : sprintf(self::URL, config('app.url'), 'store_admin_id=' . $store_admin_id, $appid);
    }

    /**
     * @param SkinRecordBonus $skinRecordBonus
     *
     * @return array
     */
    private function getBonusNoticeMessage(SkinRecordBonus $skinRecordBonus): array
    {
        return [
            'first'    => '恭喜您中奖了',
            'keyword1' => '魔妆镜测肤领红包',
            'keyword2' => '￥' . number_format($skinRecordBonus->amount, 2),
            'keyword3' => (string) now(),
            'remark'   => $this->getRemarkByPayType($skinRecordBonus),
        ];
    }

    /**
     * @param SkinRecordBonus $skinRecordBonus
     *
     * @return StoreAdmin|WechatUser|mixed|null
     */
    private function getBonusWechatUser(SkinRecordBonus $skinRecordBonus)
    {
        return $skinRecordBonus->isToStoreAdmin()
            ? $skinRecordBonus->bonusable->wechatUser
            : $skinRecordBonus->bonusable;
    }

    /**
     * @param SkinRecordBonus $skinRecordBonus
     *
     * @return string
     */
    private function getRemarkByPayType(SkinRecordBonus $skinRecordBonus): string
    {
        if ($skinRecordBonus->isUseWxPay()) {
            return $this->getWxPayNoticeRemark($skinRecordBonus);
        } elseif ($skinRecordBonus->isUseAliPay()) {
            return $this->getAlipayNoticeRemark($skinRecordBonus);
        } else {
            return "红包类型错误未能成功发放(SRB_ID: {$skinRecordBonus->id})";
        }
    }

    /**
     * @param SkinRecordBonus $skinRecordBonus
     *
     * @return string
     */
    private function getWxPayNoticeRemark(SkinRecordBonus $skinRecordBonus): string
    {
        $bonus_id = $skinRecordBonus->id;

        switch ($skinRecordBonus->bonus_status) {
            case SkinRecordBonus::BONUS_STATUS_SUCCESS :
                return "详情请查看微信支付入账通知";

            case SkinRecordBonus::BONUS_STATUS_FAILED :
                return $this->assembleWxPayFailedNoticeRemark($skinRecordBonus);

            default :
                return "微信红包未能成功发放(SRB_ID: {$bonus_id})";
        }
    }

    /**
     * @param SkinRecordBonus $skinRecordBonus
     *
     * @return string
     */
    private function getAlipayNoticeRemark(SkinRecordBonus $skinRecordBonus): string
    {
        $bonus_id = $skinRecordBonus->id;
        $staff_ali_acnt = $skinRecordBonus->bonusable->ali_account ?? '未知账户';

        switch ($skinRecordBonus->bonus_status) {
            case SkinRecordBonus::BONUS_STATUS_SUCCESS :
                return "已通过支付宝转账至： {$staff_ali_acnt}，请注意查收";

            case SkinRecordBonus::BONUS_STATUS_FAILED :
                $error_msg = $skinRecordBonus->data['alipay_fund_trans_toaccount_transfer_response']['sub_msg']
                    ?? '发生未知错误';
                return "通过支付宝转账至： {$staff_ali_acnt} 发生错误，$error_msg (SRB_ID: $bonus_id)";

            case SkinRecordBonus::BONUS_STATUS_UNSENT :
                return "未能参与抽奖：请正确填写您的支付宝账号 (SRB_ID: $bonus_id)";

            default :
                return "通过支付宝转账至： {$staff_ali_acnt} 发生错误，红包未能成功发放 (SRB_ID: {$bonus_id})";
        }
    }

    /**
     * @param SkinRecord $skinRecord
     * @param int $pay_type
     *
     * @return int|mixed
     */
    private function getWechatUserBonusAmount(SkinRecord $skinRecord, int $pay_type)
    {
        $use_ali = $pay_type === SkinRecordBonus::BONUS_ALI_PAY_TYPE;
        $ali_acnt = $skinRecord->wechatUser->ali_account ?? null;

        if ($use_ali && ! $ali_acnt) { // in case no ali
            $amount = 0;
        } else {
            $amount = Random::lottery([
                '1' => [1, 2], // 100%
            ]);
        }

        return $amount;
    }

    private function assembleWxPayFailedNoticeRemark(SkinRecordBonus $skinRecordBonus): string
    {
        /**
         * @link https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 - 错误码 - 错误代码
         *
         * @var string|null $err_code
         */
        $err_code = $skinRecordBonus->data['err_code'] ?? null;
        $bonus_id = $skinRecordBonus->id;
        $fail_prefix = '微信红包发放失败';

        switch ($err_code) {
            case "SENDNUM_LIMIT" :
                return "{$fail_prefix}，账户已到达每日发放次数上限(SRB_ID: {$bonus_id})";

            case "NOTENOUGH" :
                return "{$fail_prefix}，今日奖池已空，请明日再接再厉(SRB_ID: {$bonus_id})";

            default :
                $ret_msg = $skinRecordBonus->data['return_msg'] ?? '发生未知错误';
                $error_desc = $skinRecordBonus->data['err_code_des'] ?? '';

                return "{$fail_prefix}，{$error_desc}{$ret_msg}(SRB_ID: {$bonus_id})";
        }
    }
}
